package org.eclipse.gittab.validation;

import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.gittab.gitTabDsl.Beat;
import org.eclipse.gittab.gitTabDsl.ChordDefinition;
import org.eclipse.gittab.gitTabDsl.Fret;
import org.eclipse.gittab.gitTabDsl.GitTabDslPackage;
import org.eclipse.gittab.gitTabDsl.GuitarStr;
import org.eclipse.gittab.gitTabDsl.NoteValues;
import org.eclipse.gittab.gitTabDsl.Tab;
import org.eclipse.gittab.gitTabDsl.TabModel;
import org.eclipse.gittab.gitTabDsl.Tone;
import org.eclipse.gittab.gitTabDsl.Tuning;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.CheckType;

public class GitTabDslJavaValidator extends AbstractGitTabDslJavaValidator {

	public static final int MAX_STRINGS = 7;
	public static final int MIN_STRINGS = 4;

	public static final int MAX_FRETS = 24;
	public static final int MIN_FRETS = 12;

	@Check
	public void checkChordDefinitionStartsWithCapital(ChordDefinition chordDef) {
		if (!Character.isUpperCase(chordDef.getName().charAt(0))) {
			warning("Chordnames should start with a capital",
					GitTabDslPackage.CHORD_DEFINITION__NAME);
		}
	}

	@Check
	public void checkChordDefinitionNotes(ChordDefinition chordDef) {
		if (chordDef.getNotes().size() < 2) {
			error("Chords should be composed at of least two notes",
					GitTabDslPackage.CHORD_DEFINITION__NOTES);
		}
	}

	@Check
	public void checkFirstToneHasSoundInTab(Tab tab) {

		if (tab.getBeat() != null) {

			Beat firstBeat = tab.getBeat().get(0);

			if (firstBeat.getNotes().size() > 0) {

				Tone firstTone = firstBeat.getNotes().get(0);

				if (firstTone.getSound() == null) {
					error(
							"The first tone of a tabulator must have the note specified",
							GitTabDslPackage.BEAT__NOTES);
				}

			}

		}
	}

	@Check
	public void checkFretWithinRange(Fret fret) {

		int fretsNr = EcoreUtil2.getContainerOfType(fret, TabModel.class)
				.getTuning().getFrets();

		if (fret.getFret() < 0 || fret.getFret() > fretsNr) {
			error(MessageFormat.format(
					"Fret number must be between {0} and {1}", 0, fretsNr),
					GitTabDslPackage.FRET__FRET);
		}
	}

	@Check
	public void checkNumberOfFrets(Tuning tuning) {

		int nr = tuning.getFrets();
		if (nr < MIN_FRETS || nr > MAX_FRETS) {
			error(
					MessageFormat
							.format(
									"A guitar must have at least {0} frets and at most {1} frets",
									MIN_FRETS, MAX_FRETS),
					GitTabDslPackage.TUNING__STRINGS);
		}
	}

	@Check
	public void checkNumberOfStrings(Tuning tuning) {

		int nr = tuning.getStrings().size();
		if (nr < MIN_STRINGS || nr > MAX_STRINGS) {
			error(
					MessageFormat
							.format(
									"A guitar must have at least {0} strings and at most {1} strings",
									MIN_STRINGS, MAX_STRINGS),
					GitTabDslPackage.TUNING__STRINGS);
		}
	}

	@Check
	public void checkGuitarStrNameUniqueWithinTuning(Tuning tuning) {
		int stringsNr = tuning.getStrings().size();
		// a set can contain an element only once
		Set<String> namesSet = new HashSet<String>(stringsNr);

		for (GuitarStr string : tuning.getStrings()) {
			namesSet.add(string.getName());
		}
		if (namesSet.size() != stringsNr) {
			error("The names of the strings must be unique within a tuning",
					GitTabDslPackage.TUNING__STRINGS);
		}
	}

	@Check(CheckType.NORMAL)
	public void checkNoteValuesFitInTact(Beat beat) {

		int tactNum, tactDenom;
		double sumDuration = 0.0;

		final Tab tab = EcoreUtil2.getContainerOfType(beat, Tab.class);

		tactNum = tab.getNum();
		tactDenom = tab.getDenom();

		for (Tone tone : beat.getNotes()) {
			sumDuration += getDecimalQuater(tone.getValue());
		}

		if (sumDuration != (double) tactNum / (double) tactDenom) {
			error(MessageFormat.format("The given tact is not a {0}/{1} tact",
					tactNum, tactDenom), GitTabDslPackage.BEAT__NOTES);
		}
	}

	// @Check(CheckType.NORMAL)
	// TODO
	public void checkStrokesFitInTact(Beat beat) {

		int tactNum, tactDenom;
		double sumNoteValues = 0;

		final Tab tab = EcoreUtil2.getContainerOfType(beat, Tab.class);

		tactNum = tab.getNum();
		tactDenom = tab.getDenom();

		for (Tone tone : beat.getNotes()) {

			int noteValue = tactDenom * tone.getStrokes().size();

			sumNoteValues += (double) tactDenom / (double) noteValue;
		}

		if (sumNoteValues != tactNum) {
			error(MessageFormat.format(
					"The given stroke numbers do not fit into a {0}/{1} tact",
					tactNum, tactDenom), GitTabDslPackage.BEAT__NOTES);
		}
	}

	/**
	 * The correct duration value has to be calculated and converted to a
	 * decimal value.
	 */
	private double getDecimalQuater(NoteValues noteValue) {
		return 1 / Math.pow(2, noteValue.getValue());
	}
}
